/**
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
** \file main.cpp
** \version $Id: main.cpp 25 2009-10-28 16:17:47Z rigdern $
*/

#include <iostream>
#include <string>

/* Boost libraries (for command-line arguments) */
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/parsers.hpp>
#include <boost/program_options/variables_map.hpp>

/* Data libraries */
#include <common/data/Table.h>

/* SQlite libraries */
#include <common/sqlite/sqlite3.h>

/* Utilities */
#include <common/utilities/DistanceVector.h>

/* Algorithms */
#include <algorithms/samarati/samarati.h>
#if 0
  #include <algorithms/bayardoaggarwal/bayardoaggarwal.h>
  #include <algorithms/incognito/incognito.h>
  #include <algorithms/mondrian/mondrian.h>
#endif

bool
parseCommandLineArguments(int argc, char** argv, std::string& databaseFile, 
                          std::string& algorithm, unsigned int& kValue, 
                          unsigned int& sValue)
{
  /* Parse command line options using Boost. */
  boost::program_options::options_description desc("Available Options");
  desc.add_options()
    ("help",  "Optional:: Produces help message")
    ("database,d", boost::program_options::value<std::string>(&databaseFile),
     "Required:: Location of the SQlite database file")
    ("kvalue,k", boost::program_options::value<unsigned int>(&kValue),
     "Required:: Value of k for the k-anonymity algorithms")
    ("suppression,s", boost::program_options::value<unsigned int>(&sValue),
     "Required:: Maximum number of suppressable tuples in the database")
    ("algorithm,a", boost::program_options::value<std::string>(&algorithm),
     "Required:: Algorithm used to generalize the dataset.")
    ;

  boost::program_options::variables_map variablesMap;
  try {
    boost::program_options::store(
      boost::program_options::parse_command_line(argc, argv, desc), variablesMap
    );
    boost::program_options::notify(variablesMap);
  } catch (boost::program_options::error e) {
    std::cerr << "Error: " << e.what() << std::endl;
    return false;
  }

  /* Check to see if required arguments were included */
  bool errorsDontExist = true;
  if (variablesMap.count("help")) {
    std::cout << desc << "\n";
    return false;
  }
  if (!variablesMap.count("algorithm")) {
    std::cerr << "Error: --algorithm (-a) a required value."
              << std::endl;
    errorsDontExist = false;
  }
  if (!variablesMap.count("database")) {
    std::cerr << "Error: --database (-d) a required value."
              << std::endl;
    errorsDontExist = false;
  }
  if (!variablesMap.count("kvalue")) {
    std::cerr << "Error: --kvalue (-k) a required value."
              << std::endl;
    errorsDontExist = false;
  }
  if (!variablesMap.count("suppression")) {
    std::cerr << "Error: --suppression (-s) a required value."
              << std::endl;
    errorsDontExist = false;
  }

  return errorsDontExist;
}

/* Begin temporary tests */

#include <common/attributes/DataSet.h>
#include <common/attributes/ZipCodeAttribute.h>
#include <common/attributes/RaceAttribute.h>
#include <stdexcept>
#include <sstream>

std::string vecToString(DistanceVector dv)
{
  DistanceVector::const_iterator it;
  std::string ret = "";
  for (it = dv.begin(); it != dv.end(); ++it) {
    std::ostringstream os;
    os << *it;
    ret += os.str() + " ";
  }
  return ret;
}

void test(sqlite3 *db)
{
  ZipCodeAttribute attr("zip");
  RaceAttribute attr2("race");
  
  std::vector<BasicAttribute*> attrs;
  attrs.push_back(&attr2);
  attrs.push_back(&attr);
  DataSet dataSet(db, attrs);
  
  std::cout << "Debug----------" << std::endl;
  std::cout << "Maximal Vector: " << vecToString(dataSet.maximalVector()) << std::endl;
  std::cout << "Attribute Name: [" << attr.name() << "]" << std::endl;
  std::cout << "Attribute Height: " << attr.height() << std::endl;
  try {
    for (int i = 0; i <= 6; ++i) {
      std::string zip = "12180";
      attr.generalizeString(zip, i);
      std::cout << i << ": " << zip << std::endl;
    }
  } catch (std::domain_error e) {
    std::cout << "ZipCodeAttribute::generalizeString threw an exception: "
      << e.what() << std::endl;
  }
  std::cout << "---------------" << std::endl;
  std::cout << "Attribute Name: [" << attr2.name() << "]" << std::endl;
  std::cout << "Attribute Height: " << attr2.height() << std::endl;
  try {
    for (int i = 0; i <= 2; ++i) {
      std::string value = "white";
      attr2.generalizeString(value, i);
      std::cout << i << ": " << value << std::endl;
    }
  } catch (std::logic_error e) {
    std::cout << "RaceAttribute::generalizeString threw an exception: "
      << e.what() << std::endl;
  }
  std::cout << std::endl;
  try {
    for (int i = 0; i <= 2; ++i) {
      std::string value = "black";
      attr2.generalizeString(value, i);
      std::cout << i << ": " << value << std::endl;
    }
  } catch (std::logic_error e) {
    std::cout << "RaceAttribute::generalizeString threw an exception: "
      << e.what() << std::endl;
  }
  std::cout << std::endl;
  try {
    for (int i = 0; i <= 2; ++i) {
      std::string value = "asian";
      attr2.generalizeString(value, i);
      std::cout << i << ": " << value << std::endl;
    }
  } catch (std::logic_error e) {
    std::cout << "RaceAttribute::generalizeString threw an exception: "
      << e.what() << std::endl;
  }
  std::cout << std::endl;
  try {
    for (int i = 0; i <= 1; ++i) {
      std::string value = "person";
      attr2.generalizeString(value, i);
      std::cout << i << ": " << value << std::endl;
    }
  } catch (std::logic_error e) {
    std::cout << "RaceAttribute::generalizeString threw an exception: "
      << e.what() << std::endl;
  }
  dataSet.dumpGeneralizationTable();
  for (int i = 0; i <= 1; ++i) {
    for (int j = 0; j <= 2; ++j) {
      int suppressionCount;
      DistanceVector v;
      v.push_back(i);
      v.push_back(j);
      suppressionCount = dataSet.suppressionsForGeneralization(v, 2);
      std::cout << "[ " << vecToString(v) << "]: " << suppressionCount << " suppressions." << std::endl;
      if (i == 0 && j == 1) dataSet.dumpGeneralizedTable(v, 2);
    }
  }
  std::cout << "End Debug------" << std::endl << std::endl;
}

/* End temporary tests */

int 
main(int argc, char** argv)
{
  std::string databaseFile;
  std::string algorithm;
  unsigned int kValue;
  unsigned int sValue;

  if (!parseCommandLineArguments(argc,argv,databaseFile,
                                 algorithm,kValue,sValue)) {
    return 1;
  }

  /* Open Database to be used */
  sqlite3* inputDB;
  if (sqlite3_open_v2(databaseFile.c_str(), &inputDB, 
		      SQLITE_OPEN_READONLY, NULL)) {
    std::cerr << "Error: Can't open database: " << sqlite3_errmsg(inputDB) 
	      << std::endl;
    sqlite3_close(inputDB);
    return 1;
  }

  /* Select algorithm to run testing on */
  Table* outputTable = NULL;
  if (algorithm == "none") {
    outputTable = new Table(inputDB);
  } else if (algorithm == "samarati") {
    #if 1
    test(inputDB); // temporary line
    #else
    DistanceVector solVector;
    DistanceVector::const_iterator it;
    RaceAttribute race("race");
    ZipCodeAttribute zip("zip");

    std::vector<BasicAttribute*> attrs;
    attrs.push_back(&race);
    attrs.push_back(&zip);
    DataSet dataSet(inputDB, attrs);
    
    solVector = samarati(dataSet, kValue, sValue);
    for (it = solVector.begin(); it != solVector.end(); ++it)
      std::cout << *it << " ";
      std::cout << std::endl;
    #endif
  }
  
  /* Read the Database to be used in and close it */
  sqlite3_close(inputDB);

  /* Perform statistical analysis */

  if (outputTable)
    delete outputTable;

  return 0;
}
